<template>
  <div class="catalog-card" v-if="titles.length > 0">
    <div class="catalog-card-header">
      <div>
        <span></span>
        <span>目录</span>
      </div>
      <span class="progress">{{ progress }}</span>
    </div>

    <div class="catalog-content">
      <div
        v-for="title in titles"
        :key="title.id"
        @click="scrollToView(title.scrollTop)"
        :class="[
          'catalog-item',
          currentTitle.id == title.id ? 'active' : 'not-active',
        ]"
        :style="{ marginLeft: title.level * 20 + 'px' }"
        v-show="title.isVisible"
        :title="title.rawName"
      >
        {{ title.name }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
const props = defineProps({
  container: {
    type: String,
    default: "",
  },
  scrollQuer: {
    type: String,
    default: "#layout-app .n-layout-scroll-container",
  },
  show: {
    type: Boolean,
    default: false,
  },
});
interface titlesType {
  id: string;
  isVisible: boolean;
  level: number;
  name: number;
  parent?: titlesType | null;
  rawName: string;
  scrollTop: number;
  children: titlesType[];
}
const titles = ref<titlesType[]>([]);
const progress = ref("");
const currentTitle = reactive({ id: "" });
// 滚动的元素
let scrollObj: Element | null = null;
// 文档元素
let articleElement: Element | null = null;

onMounted(async () => {
  await nextTick();
  titles.value = await getTitles(props.container);
  addScroll();
});
function addScroll() {
  if (!scrollObj) scrollObj = document.querySelector(props.scrollQuer);
  // 监听滚动事件并更新样式
  if (scrollObj)
    scrollObj.addEventListener("scroll", function () {
      if (!scrollObj || !articleElement) return;
      const progress_number =
        (scrollObj.scrollTop / articleElement.scrollHeight) * 100;
      progress.value =
        parseInt((progress_number < 100 ? progress_number : 100) + "") + "%";

      let visibleTitles = [];

      for (let i = titles.value.length - 1; i >= 0; i--) {
        const title = titles.value[i];
        if (title.scrollTop <= scrollObj.scrollTop) {
          if (currentTitle.id === title.id) return;

          Object.assign(currentTitle, title);

          // 展开节点
          setChildrenVisible(title, true);
          visibleTitles.push(title);

          // 展开父节点
          let parent = title.parent;
          while (parent) {
            setChildrenVisible(parent, true);
            visibleTitles.push(parent);
            parent = parent.parent;
          }

          // 折叠其余节点
          for (const t of titles.value) {
            if (!visibleTitles.includes(t)) {
              setChildrenVisible(t, false);
            }
          }

          return;
        }
      }
    });
}
async function getTitles(container: string) {
  let titlesArr: any[] = [];
  let levels = ["h1", "h2", "h3"];
  if (!articleElement) {
    articleElement = document.querySelector(container);
  }
  if (!articleElement) {
    return titlesArr;
  }
  let elements = Array.from(
    articleElement.querySelectorAll("*")
  ) as HTMLElement[];
  // 调整标签等级
  let tagNames = new Set(elements.map((el) => el.tagName.toLowerCase()));
  for (let i = levels.length - 1; i >= 0; i--) {
    if (!tagNames.has(levels[i])) {
      levels.splice(i, 1);
    }
  }
  let serialNumbers = levels.map(() => 0);
  for (let i = 0; i < elements.length; i++) {
    const element = elements[i];
    let tagName = element.tagName.toLowerCase();
    let level = levels.indexOf(tagName);
    if (level == -1) continue;

    let id = tagName + "-" + element.innerText + "-" + i;
    let node = {
      id,
      level,
      parent: null,
      children: [],
      rawName: element.innerText,
      scrollTop: element.offsetTop,
      isVisible: false,
      name: "",
    };

    if (titlesArr.length > 0) {
      let lastNode = titlesArr.at(-1);

      // 遇到子标题
      if (lastNode.level < node.level) {
        node.parent = lastNode;
        lastNode.children.push(node);
      }
      // 遇到上一级标题
      else if (lastNode.level > node.level) {
        serialNumbers.fill(0, level + 1);
        let parent = lastNode.parent;
        while (parent) {
          if (parent.level < node.level) {
            parent.children.push(node);
            node.parent = parent;
            break;
          }
          parent = parent.parent;
        }
      }
      // 遇到平级
      else if (lastNode.parent) {
        node.parent = lastNode.parent;
        lastNode.parent.children.push(node);
      }
    }

    serialNumbers[level] += 1;
    let serialNumber = serialNumbers.slice(0, level + 1).join(".");

    node.isVisible = node.parent == null;
    node.name = serialNumber + ". " + element.innerText;
    titlesArr.push(node);
  }
  return titlesArr;
}

// 设置子节点的可见性
function setChildrenVisible(title: { children: any }, isVisible: boolean) {
  for (const child of title.children) {
    child.isVisible = isVisible;
  }
}

// 滚动到指定的位置
async function scrollToView(scrollTop: any) {
  if (!scrollObj) scrollObj = document.querySelector(props.scrollQuer);
  if (scrollObj) scrollObj.scrollTo({ top: scrollTop, behavior: "smooth" });
}
</script>
<style lang="scss" scoped>
.catalog-card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 3px 8px 6px rgba(7, 17, 27, 0.05);
  padding: 20px 24px;
  margin-top: 25px;
  box-sizing: border-box;
}

.catalog-card-header {
  text-align: left !important;
  margin-bottom: 15px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.catalog-icon {
  font-size: 18px;
  margin-right: 10px;
  color: dodgerblue;
}

.catalog-card-header div > span {
  font-size: 17px;
  color: #4c4948;
}

.progress {
  color: #a9a9a9;
  font-style: italic;
  font-size: 140%;
}

.catalog-content {
  max-height: calc(100vh - 120px);
  overflow: auto;
  margin-right: -24px;
  padding-right: 20px;
}

.catalog-item {
  color: #666261;
  margin: 5px 0;
  line-height: 28px;
  cursor: pointer;
  transition: all 0.2s ease-in-out;
  font-size: 14px;
  padding: 2px 6px;
  display: -webkit-box;
  overflow: hidden;
  text-overflow: ellipsis;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;

  &:hover {
    color: #1892ff;
  }
}

.active {
  background-color: #000;
  border-radius: 5px;
  color: white;

  &:hover {
    background-color: #0c82e9;
    color: white;
  }
}
</style>
